import { Component, inject, ViewChild, ElementRef, AfterViewInit, OnInit } from '@angular/core';
import { DrawerModule } from 'primeng/drawer';
import { ToastModule } from 'primeng/toast';
import { ContextMenuModule, ContextMenu } from 'primeng/contextmenu';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { InputTextModule } from 'primeng/inputtext';
import { InputNumberModule } from 'primeng/inputnumber';
import { FormsModule } from '@angular/forms';
import { ColorPickerModule } from 'primeng/colorpicker';
import { CheckboxModule } from 'primeng/checkbox';
import { MenuItem, ConfirmationService, MessageService } from 'primeng/api';
import { g } from '../service/global-data';
import { default_map_list } from '../service/map-source';
import { ElectronService } from '@/service/electron.service';
import { TransformService } from '../service/transform.service';
import { JiupianService } from '../service/jiupian.service';
import { TrackLineService } from '../service/track-line.service';
import { MapApiService } from '../service/map-api-service';
import { SrtmService } from '../service/srtm.service';
import { MapSource } from '../data-model/map-source.model';
import { TrackLine } from '../data-model/track-line.model';
import { EditTrackLine } from '../data-model/edit-track-line.model';
import { WayPoint } from '../data-model/way-point.model';
import { Photo } from '../data-model/photo.model';
import { wpt_images } from '../service/wpt-pin32-data';
import { v4 as uuid } from 'uuid';


/*
# 统一本程序中位置坐标命名：
# (lon, lat)             : WGS-84坐标系中的经纬度坐标
# 在本应用中，将最大缩放级别下的Web墨卡托投影像素地图称为全图，
#            将某一位置在全图中对应的像素坐标标记为(fpx, fpy)。
#            在已知某一坐标点的全图像素坐标以及缩放级别后，就可以唯一确定该坐标点的当前显示位置，找到其对应的瓦片地图
# (fpx, fpy, zoom)       : 全图像素坐标及缩放级别（坐标原点在左上角），唯一确定一个显示位置
# (px, py, zoom)         : 缩放图图像素坐标及缩放级别（坐标原点在左上角）
# (spx, spy)             : 地图位置在当前屏幕窗口中对应的像素坐标（坐标原点在左上角）
# (tile_x, tile_y, zoom) : 在缩放级别zoom下，Google瓦片坐标系中的瓦片坐标（坐标原点在左上角）
# (tile_spx, tile_spy)   : 地图瓦片左上角在当前屏幕窗口中的像素坐标（坐标原点在左上角）
# (width, height)        : 当前屏幕窗口的宽高度
*/

@Component({
    selector: 'app-map-canvas',
    imports: [DrawerModule, ContextMenuModule, ContextMenu, ConfirmDialogModule, InputTextModule, InputNumberModule, FormsModule,
        ColorPickerModule, CheckboxModule, ToastModule
    ],
    providers: [ConfirmationService, MessageService, ElectronService, TransformService, JiupianService, TrackLineService, MapApiService, SrtmService],
    templateUrl: './map-canvas.html',
})
export class MapCanvas implements AfterViewInit, OnInit {
    @ViewChild('context_menu') context_menu!: ContextMenu;
    @ViewChild('map_canvas', { static: true }) canvasRef!: ElementRef<HTMLCanvasElement>;
    canvas!: HTMLCanvasElement;
    ctx!: CanvasRenderingContext2D;
    resizeObserver!: ResizeObserver;
    map_canvas_parent_dom: any;

    backup_canvas!: HTMLCanvasElement;
    backup_ctx!: CanvasRenderingContext2D;

    confirmationService = inject(ConfirmationService);
    messageService = inject(MessageService);
    electronService = inject(ElectronService);
    transformService = inject(TransformService);
    jiupianService = inject(JiupianService);
    trackLineService = inject(TrackLineService);
    mapApiService = inject(MapApiService);
    srtmService = inject(SrtmService);

    global_data = g;

    tile_cache = new Map();
    loading_tiles = new Set();

    width!: number;
    height!: number;
    half_width!: number;
    half_height!: number;

    zoom!: number;
    centre_fpx!: number;
    centre_fpy!: number;
    centre_px!: number;
    centre_py!: number;
    corner1_px!: number;
    corner1_py!: number;
    corner2_px!: number;
    corner2_py!: number;
    corner1_tile_x!: number;
    corner1_tile_y!: number;
    corner2_tile_x!: number;
    corner2_tile_y!: number;
    corner1_px_offset_in_tile!: number;
    corner1_py_offset_in_tile!: number;
    centre_fpx_gcj02!: number;
    centre_fpy_gcj02!: number;
    centre_px_gcj02!: number;
    centre_py_gcj02!: number;
    corner1_px_gcj02!: number;
    corner1_py_gcj02!: number;
    corner2_px_gcj02!: number;
    corner2_py_gcj02!: number;
    corner1_tile_x_gcj02!: number;
    corner1_tile_y_gcj02!: number;
    corner2_tile_x_gcj02!: number;
    corner2_tile_y_gcj02!: number;
    corner1_px_gcj02_offset_in_tile!: number;
    corner1_py_gcj02_offset_in_tile!: number;

    dragging_map = false;

    show_track_line_select = false;
    nearby_track_lines: any[] = [];
    selected_track_line_uuid!: string;

    context_menu_event: any = null;
    context_menu_items!: MenuItem[];
    track_line_menu_items: MenuItem[] = [
        {
            label: '复制到编辑区',
            command: (event) => g.user_data.copy_to_track_edit(),
        },
        {
            separator: true,
        },
        {
            label: '保存为kml文件',
            command: (event) => g.user_data.save_kml_file(),
        },
        {
            separator: true,
        },
        {
            label: '编辑轨迹参数',
            command: (event) => {
                this.track_line_color = '#' + g.user_data.selected.red.toString(16).padStart(2, '0')
                    + g.user_data.selected.green.toString(16).padStart(2, '0')
                    + g.user_data.selected.blue.toString(16).padStart(2, '0');
                this.show_track_line_edit = true;
            },
        },
        {
            separator: true,
        },
        {
            label: '隐藏轨迹',
            command: (event) => {
                g.user_data.selected.visible = false;
                g.visible_tracks.delete(g.user_data.selected.uuid);
                this.repaint();
                g.track_chart.refresh();
                g.track_info.refresh();
            },
        },
        {
            separator: true,
        },
        {
            label: '删除轨迹',
            command: (event) => g.user_data.delete_track_line(),
        },
    ];
    way_point_menu_items: MenuItem[] = [
        {
            label: '更改图标',
            command: (event) => {
                g.user_data.show_select_wpt_pin_dialog = true;
            },
        },
        {
            separator: true,
        },
        {
            label: '隐藏路点',
            command: (event) => {
                g.user_data.toggle_way_point_display();
            },
        },
        {
            separator: true,
        },
        {
            label: '删除路点',
            command: (event) => {
                g.user_data.delete_way_point();
            },
        },
    ];
    photo_menu_items: MenuItem[] = [
        {
            label: '显示大图',
            command: (event) => {
                this.show_big_photo = true;
            },
        },
        {
            separator: true,
        },
        {
            label: '删除照片',
            command: (event) => {
                g.photos.delete(g.selected_photo.uuid);
                g.selected_photo = null;
                this.repaint();
                g.track_chart.refresh();
                g.track_info.refresh();
            },
        },
    ];
    map_menu_items: MenuItem[] = [
        {
            label: '进入编辑模式',
            command: (event) => {
                g.header.toggle_edit_mode();
            },
        },
        {
            separator: true,
        },
        {
            label: '截图(Ctrl-P)',
            command: (event) => {
                this.clip_map_canvas();
            },
        },
        {
            separator: true,
        },
        {
            label: '放置路点',
            command: (event) => {
                this.place_way_point();
            },
        },
    ];
    wait_track_line_selected = false;
    photo_or_way_point_selected = false;

    edit_track_line_menu_items: MenuItem[] = [
        {
            label: '移动至<用户数据>',
            command: (event) => {
                g.track_edit.move_to_user_data();
            },
        },
        {
            separator: true,
        },
        {
            label: '填充高程数据',
            command: (event) => {
                g.track_edit.fill_track_line_altitudes();
            },
        },
        {
            separator: true,
        },
        {
            label: '删除高程数据',
            command: (event) => {
                g.track_edit.delete_track_line_altitudes();
            },
        },
        {
            separator: true,
        },
        {
            label: '轨迹起始点翻转',
            command: (event) => {
                g.track_edit.reverse_track_line();
            },
        },
        {
            separator: true,
        },
        {
            label: '调整当前轨迹点',
            command: (event) => {
                this.dragging_point = true;
                this.canvas.style.cursor = 'crosshair';
            },
        },
        {
            separator: true,
        },
        {
            label: '编辑当前轨迹点',
            command: (event) => {
                g.track_edit.edit_track_line_point();
            },
        },
        {
            separator: true,
        },
        {
            label: '删除轨迹',
            command: (event) => {
                g.header.delete_track_line();
            },
        },
    ];
    edit_map_menu_items: MenuItem[] = [
        {
            label: '退出编辑模式',
            command: (event) => {
                g.header.toggle_edit_mode();
            },
        },
        {
            separator: true,
        },
        {
            label: '自绘轨迹',
            command: (event) => {
                this.enter_self_drawing();
            },
        },
    ];

    show_track_line_edit = false;
    track_line_color: string = '#0000ff';

    edit_pos_to_track_lines = new Map();

    self_drawing = false;
    self_drawing_track_line: EditTrackLine | null = null;
    self_drawing_end_pos: any = null;
    dragging_point = false;

    pos_to_waypoint_or_photo = new Map();
    show_big_photo = false;
    photo_drawing_count = 0;


    ngAfterViewInit(): void {
        this.load_map_config();

        this.canvas = this.canvasRef.nativeElement;
        this.ctx = this.canvas.getContext('2d')!;

        // 离屏Canvas
        this.backup_canvas = document.createElement('canvas');
        this.backup_ctx = this.backup_canvas.getContext('2d')!;

        this.map_canvas_parent_dom = document.getElementById('map_canvas_parent_dom');

        this.setupEventListeners();
        this.setupResizeObserver();
    }

    ngOnInit(): void {
        g.map_canvas = this;
    }

    async load_map_config() {
        let first = true;

        try {
            const map_config = await this.electronService.invoke('load-map-config');
            let map_list = [];
            if (!!map_config) {
                this.zoom = map_config.zoom;
                this.centre_fpx = map_config.centre_fpx;
                this.centre_fpy = map_config.centre_fpy;
                map_list = map_config.map_source_list;
                first = false;
            } else {
                map_list = structuredClone(default_map_list);
                first = true;
            }

            for (const map_source of map_list) {
                g.map_source_list.push(map_source);
            }

            if (first) {
                this.zoom_to_fit_China();  // 第一次打开App，显示中国地图
            } else {
                this.zoom_to_fpx_fpy(this.zoom, this.centre_fpx, this.centre_fpy); // 后续打开App，按上一次关闭时的配置显示地图
            }

            g.footer.refresh_map_sources();
        } catch (error) {
        }
    }

    // 设置窗口大小调整监听器
    setupResizeObserver() {
        this.resizeObserver = new ResizeObserver((entries) => {
            if (entries.length > 0) {
                // 根据容器调整大小
                const container = this.canvas.parentElement!;
                this.canvas.width = container.offsetWidth;
                this.canvas.height = container.offsetHeight;
                this.width = this.canvas.width;
                this.height = this.canvas.height;
                this.half_width = Math.floor(this.width / 2);
                this.half_height = Math.floor(this.height / 2);

                // 重绘地图
                if (!!this.zoom) {
                    this.zoom_to_fpx_fpy(this.zoom, this.centre_fpx, this.centre_fpy);
                }
            }
        });
        this.resizeObserver.observe(this.canvas);
    }

    // 设置事件监听器
    setupEventListeners() {
        let isDragging = false;
        let lastX: number, lastY: number;

        this.canvas.addEventListener('mousedown', (event) => {
            // 只针对左键. 左键：button=0; 中键：button=1; 右键：button=2
            if (event.button !== 0) return;

            isDragging = true;
            lastX = event.offsetX;
            lastY = event.offsetY;
            this.canvas.style.cursor = 'grabbing';

            if (g.editing) {
                if (this.self_drawing) {
                    const spx = event.offsetX;
                    const spy = event.offsetY;
                    const lon = this.get_lon_from_spx(spx);
                    const lat = this.get_lat_from_spy(spy);
                    const track_point = [
                        lon,
                        lat,
                        0,
                        (new Date()).toISOString(),
                        this.transformService.get_fpx_from_longitude(lon),
                        this.transformService.get_fpy_from_latitude(lat)
                    ];
                    this.self_drawing_track_line!.points!.push(track_point);
                    this.trackLineService.compute_track_line_args(this.self_drawing_track_line!);
                    this.repaint();
                }

                if (this.dragging_point) {
                    const spx = event.offsetX;
                    const spy = event.offsetY;
                    const lon = this.get_lon_from_spx(spx);
                    const lat = this.get_lat_from_spy(spy);
                    const fpx = this.transformService.get_fpx_from_longitude(lon);
                    const fpy = this.transformService.get_fpy_from_latitude(lat);

                    const point = g.track_edit.selected_track_line.points[g.track_edit.selected_track_line.selected_point];
                    point[0] = lon;
                    point[1] = lat;
                    point[4] = fpx;
                    point[5] = fpy;
                    this.trackLineService.compute_track_line_args(g.track_edit.selected_track_line);

                    this.self_drawing_end_pos = null;
                    this.dragging_point = false;

                    this.repaint();
                    g.track_chart.refresh();
                    g.track_info.refresh();
                }
            }
        });

        this.canvas.addEventListener('mousemove', (event) => {
            // 判断鼠标移动时左键是否按下
            if (event.buttons !== 1) {
                isDragging = false;
                this.canvas.style.cursor = 'default';
            }

            if (isDragging) {
                this.dragging_map = true;

                const delta_px = lastX - event.offsetX;
                const delta_py = lastY - event.offsetY;

                if (Math.abs(delta_px) < g.PAN_THRESHOLD && Math.abs(delta_py) < g.PAN_THRESHOLD) {
                    return;
                }

                lastX = event.offsetX;
                lastY = event.offsetY;

                this.pan(delta_px, delta_py);
            } else {
                const lon = this.get_lon_from_spx(event.offsetX)
                const lat = this.get_lat_from_spy(event.offsetY)
                g.footer.map_lon_lat = `东经：${lon.toFixed(8)}，北纬：${lat.toFixed(8)}`;
            }

            // 编辑状态下自绘轨迹，显示橡皮筋线条
            if (g.editing) {
                if (this.self_drawing) {
                    this.canvas.style.cursor = 'crosshair';
                    if (this.self_drawing_track_line!.points!.length > 0) {
                        this.self_drawing_end_pos = { spx: event.offsetX, spy: event.offsetY };
                        this.repaint();
                    }
                }
                if (this.dragging_point) {
                    this.self_drawing_end_pos = { spx: event.offsetX, spy: event.offsetY };
                    this.canvas.style.cursor = 'crosshair';
                    this.repaint();
                }
            }
        });

        this.canvas.addEventListener('mouseup', () => {
            isDragging = false;
            this.canvas.style.cursor = 'default';

            if (this.dragging_map) {
                this.dragging_map = false;
                this.repaint();                
            }
        });

        // 离开不做处理，注释掉
        // this.canvas.addEventListener('mouseleave', () => {
        //     isDragging = false;
        //     this.canvas.style.cursor = 'default';
        // });

        // 鼠标滚轮缩放
        this.canvas.addEventListener('wheel', (event) => {
            event.preventDefault();
            if (event.deltaY > 0) {
                this.zoom_out();
            } else {
                this.zoom_in();
            }
        });

        // 双击缩放
        this.canvas.addEventListener('dblclick', (event) => {
            if (g.editing && this.self_drawing) {
                // 注意：监听到双击时间时，已经执行了两次 mousedown 操作
                if (this.self_drawing_track_line!.points!.length < 3) {
                    // 在尚未画点前取消自绘轨迹，已有的两个点时双击自己带来的
                    while (this.self_drawing_track_line!.points!.length > 0) {
                        this.self_drawing_track_line!.points!.pop();
                    }
                    g.track_edit.track_line_list.splice(g.track_edit.track_line_list.indexOf(this.self_drawing_track_line!), 1);
                } else {
                    // 双击时在mousedown事件中已加了两个点，应弹出最后一个
                    this.self_drawing_track_line!.points!.pop();
                    this.trackLineService.compute_track_line_args(this.self_drawing_track_line!);

                    this.self_drawing_track_line!.selected_point = Math.floor(this.self_drawing_track_line!.points!.length! / 2);
                    this.self_drawing_track_line!.sel_start_idx = 0;
                    this.self_drawing_track_line!.sel_end_idx = this.self_drawing_track_line!.points!.length - 1;
                    this.self_drawing_track_line!.sel_alt_min = this.self_drawing_track_line!.alt_min;
                    this.self_drawing_track_line!.sel_alt_max = this.self_drawing_track_line!.alt_max;
                    this.self_drawing_track_line!.sel_distance = this.self_drawing_track_line!.distance;
                    this.self_drawing_track_line!.sel_distance_horizon = this.self_drawing_track_line!.distance_horizon;
                    this.self_drawing_track_line!.sel_climb = this.self_drawing_track_line!.climb;
                    this.self_drawing_track_line!.sel_descent = this.self_drawing_track_line!.descent;
                    this.self_drawing_track_line!.sel_time_begin = this.self_drawing_track_line!.time_begin;
                    this.self_drawing_track_line!.sel_time_end = this.self_drawing_track_line!.time_end;
                    this.self_drawing_track_line!.sel_duration = this.self_drawing_track_line!.duration;
                    this.self_drawing_track_line!.sel_point_num = this.self_drawing_track_line!.point_num;
                    this.self_drawing_track_line!.selected = true;
                    g.track_edit.select_track_line(this.self_drawing_track_line!.uuid);
                }

                this.self_drawing_end_pos = null;
                this.canvas.style.cursor = 'default';
                this.self_drawing = false;
                this.repaint();
            } else {
                event.preventDefault();
                this.zoom_in();
            }

        });

        // 单击选中
        this.canvas.addEventListener('click', (event) => {
            this.wait_track_line_selected = false;  // 左键单击时不需要等待
            if (g.editing) {
                if (!this.self_drawing && !this.dragging_point) {
                    this.onEditMouseClick(event.offsetX, event.offsetY);
                }
            } else {
                this.onMouseClick(event.offsetX, event.offsetY);
            }
        });

        // 右键单击弹出菜单单击选中
        this.canvas.addEventListener('contextmenu', (event) => {
            if (g.editing) {
                this.onEditContextmenu(event);
            } else {
                this.onContextmenu(event);
            }
        });

        this.map_canvas_parent_dom!.addEventListener('mouseenter', () => {
            this.map_canvas_parent_dom!.focus();
        });
        this.map_canvas_parent_dom!.addEventListener('mouseleave', () => {
            this.map_canvas_parent_dom!.blur();
        });
        this.map_canvas_parent_dom!.addEventListener('keydown', (event: any) => {
            if (g.editing) {
                if (this.self_drawing) {
                    if (event.key === 'Escape') {
                        if (this.self_drawing_track_line!.points!.length < 2) {
                            // 在尚未画点或已画一个点之前取消自绘轨迹
                            while (this.self_drawing_track_line!.points!.length > 0) {
                                this.self_drawing_track_line!.points!.pop();
                            }
                            g.track_edit.track_line_list.splice(g.track_edit.track_line_list.indexOf(this.self_drawing_track_line!), 1);
                        } else {
                            this.self_drawing_track_line!.selected_point = Math.floor(this.self_drawing_track_line!.points!.length! / 2);
                            this.self_drawing_track_line!.sel_start_idx = 0;
                            this.self_drawing_track_line!.sel_end_idx = this.self_drawing_track_line!.points!.length - 1;
                            this.self_drawing_track_line!.sel_alt_min = this.self_drawing_track_line!.alt_min;
                            this.self_drawing_track_line!.sel_alt_max = this.self_drawing_track_line!.alt_max;
                            this.self_drawing_track_line!.sel_distance = this.self_drawing_track_line!.distance;
                            this.self_drawing_track_line!.sel_distance_horizon = this.self_drawing_track_line!.distance_horizon;
                            this.self_drawing_track_line!.sel_climb = this.self_drawing_track_line!.climb;
                            this.self_drawing_track_line!.sel_descent = this.self_drawing_track_line!.descent;
                            this.self_drawing_track_line!.sel_time_begin = this.self_drawing_track_line!.time_begin;
                            this.self_drawing_track_line!.sel_time_end = this.self_drawing_track_line!.time_end;
                            this.self_drawing_track_line!.sel_duration = this.self_drawing_track_line!.duration;
                            this.self_drawing_track_line!.sel_point_num = this.self_drawing_track_line!.point_num;
                            this.self_drawing_track_line!.selected = true;
                            g.track_edit.select_track_line(this.self_drawing_track_line!.uuid);
                        }

                        this.self_drawing_end_pos = null;
                        this.canvas.style.cursor = 'default';
                        this.self_drawing = false;
                        this.repaint();
                    } else if (event.key === 'Backspace') {
                        if (this.self_drawing_track_line!.points!.length > 1) {
                            this.self_drawing_track_line!.points!.pop();
                            this.repaint();
                        }
                    }
                } else if (this.dragging_point) {
                    this.self_drawing_end_pos = null;
                    this.canvas.style.cursor = 'default';
                    this.dragging_point = false;
                    this.repaint();
                } else {
                    if (!!g.track_edit.selected_track_line) {
                        if (event.key === 'ArrowLeft' && g.track_edit.selected_track_line.selected_point > 0) {
                            g.track_edit.selected_track_line.selected_point--;
                            this.repaint();
                            g.track_chart.refresh();
                            g.track_info.refresh();
                        }
                        if (event.key === 'ArrowRight' && g.track_edit.selected_track_line.selected_point < g.track_edit.selected_track_line.point_num - 1) {
                            g.track_edit.selected_track_line.selected_point++;
                            this.repaint();
                            g.track_chart.refresh();
                            g.track_info.refresh();
                        }
                    }
                }
            } else {
                if (!!g.user_data.selected && g.user_data.selected.type === 'track_line') {
                    if (event.key === 'ArrowLeft' && g.track_chart.selected_point > 0) {
                        g.track_chart.selected_point--;
                        g.track_chart.select_point(g.track_chart.selected_point);
                    }
                    if (event.key === 'ArrowRight' && g.track_chart.selected_point < g.user_data.selected.point_num - 1) {
                        g.track_chart.selected_point++;
                        g.track_chart.select_point(g.track_chart.selected_point);
                    }
                }
                if (event.key === 'p' && event.ctrlKey) {
                    this.clip_map_canvas();
                }
                if (event.key === 'F12') {
                    this.electronService.send('open-dev-tools');
                }
            }
        });

    }

    onContextmenu(event: any) {
        this.context_menu_event = event;

        this.wait_track_line_selected = true;  // 右键单击时需要等待选择轨迹之后再弹出上下文菜单
        this.onMouseClick(event.offsetX, event.offsetY);

        // 默认选择照片和路点优先级高于轨迹
        if (this.photo_or_way_point_selected) {
            if (!!g.selected_photo) {
                this.context_menu_items = this.photo_menu_items;
            } else if (!!g.user_data.selected && g.user_data.selected.type === 'way_point') {
                this.context_menu_items = this.way_point_menu_items;
            }
            this.context_menu.show(event);
            return;
        }

        if (this.nearby_track_lines.length > 0) {
            this.context_menu_items = this.track_line_menu_items;

            const that = this;
            function wait_selection() {
                setTimeout(() => {
                    if (that.wait_track_line_selected) {
                        wait_selection();
                    } else if (!!g.user_data.selected) {
                        that.context_menu.show(event);
                    }
                }, 10);
            }
            wait_selection();
        } else {
            this.context_menu_items = this.map_menu_items;
            this.context_menu.show(event);
        }
    }

    onMouseClick(spx: number, spy: number) {
        // 默认选择路点优先级高于轨迹
        this.photo_or_way_point_selected = false;
        const pos_key = `pos-${spx}-${spy}`;
        const way_point_or_photo = this.pos_to_waypoint_or_photo.get(pos_key);
        if (!!way_point_or_photo) {
            if (way_point_or_photo.type == 'way_point') {
                g.user_data.select_way_point(way_point_or_photo);
                this.wait_track_line_selected = false;
            }
            if (way_point_or_photo.type == 'photo') {
                g.user_data.selected = null;
                g.selected_photo = way_point_or_photo;
                this.wait_track_line_selected = false;
                this.repaint_over_backup();
                g.track_chart.refresh();
                g.track_info.refresh();
            }
            this.photo_or_way_point_selected = true;
            return;
        }

        // 没选中路点再处理轨迹
        this.nearby_track_lines = [];

        for (const track_line of g.visible_tracks.values()) {
            if (track_line.visible && !this.track_line_not_in_canvas(track_line)) {
                const track_line_points = g.track_line_points_cache.get(track_line.uuid);

                let firstPoint = true;
                let spx1, spy1;
                for (const point of track_line_points) {
                    const [fpx, fpy] = [point[4], point[5]];
                    const spx2 = this.get_spx_from_fpx(fpx);
                    const spy2 = this.get_spy_from_fpy(fpy);

                    if (firstPoint) {
                        firstPoint = false;
                    } else {
                        if (spx2 === spx1 && spy2 === spy1) {
                            continue;
                        }

                        const distance = this.transformService.point_to_line_distance(spx, spy, spx1!, spy1!, spx2, spy2);
                        if (distance <= 5) {
                            this.nearby_track_lines.push(track_line);
                            break;
                        }
                    }

                    [spx1, spy1] = [spx2, spy2];
                }
            }
        }
        if (this.nearby_track_lines.length === 1) {
            this.select_track_line(this.nearby_track_lines[0].uuid);
            this.wait_track_line_selected = false;
        } else if (this.nearby_track_lines.length > 1) {
            g.user_data.selected = null;
            this.show_track_line_select = true;

            const that = this;
            function wait_selection() {
                setTimeout(() => {
                    if (that.show_track_line_select) {
                        wait_selection();
                    } else {
                        that.wait_track_line_selected = false;
                    }
                }, 10);
            }
            wait_selection();
        } else {
            this.wait_track_line_selected = false;
        }
    }

    select_track_line(track_line_uuid: string) {
        if (g.editing) {
            g.track_edit.select_track_line(track_line_uuid);
        } else {
            g.user_data.select_track_line(track_line_uuid);
        }

    }

    onEditContextmenu(event: any) {
        if (this.self_drawing) return;

        this.wait_track_line_selected = true;  // 右键单击时需要等待选择轨迹之后再弹出上下文菜单
        this.onEditMouseClick(event.offsetX, event.offsetY);

        if (this.nearby_track_lines.length > 0) {
            this.context_menu_items = this.edit_track_line_menu_items;

            const that = this;
            function wait_selection() {
                setTimeout(() => {
                    if (that.wait_track_line_selected) {
                        wait_selection();
                    } else if (!!g.track_edit.selected_track_line) {
                        that.context_menu.show(event);
                    }
                }, 10);
            }
            wait_selection();
        } else {
            this.context_menu_items = this.edit_map_menu_items;
            this.context_menu.show(event);
        }
    }

    onEditMouseClick(spx: number, spy: number) {
        this.nearby_track_lines = [];

        const near_pos = [[spx, spy], [spx - 1, spy], [spx, spy - 1], [spx + 1, spy], [spx, spy + 1], [spx - 1, spy - 1],
        [spx + 1, spy - 1], [spx - 1, spy + 1], [spx + 1, spy + 1], [spx - 2, spy - 2],
        [spx - 1, spy - 2], [spx, spy - 2], [spx + 1, spy - 2], [spx + 2, spy - 2], [spx - 2, spy - 1],
        [spx + 2, spy - 1], [spx - 2, spy], [spx + 2, spy], [spx - 2, spy + 1], [spx + 2, spy + 1],
        [spx - 2, spy + 2], [spx - 1, spy + 2], [spx, spy + 2], [spx + 1, spy + 2], [spx + 2, spy + 2]];

        for (const pos of near_pos) {
            const [spx1, spy1] = pos;
            const pos_key = 'pos-' + spx1 + '-' + spy1;
            const near_tracks = this.edit_pos_to_track_lines.get(pos_key);

            if (!!near_tracks) {
                for (const { track_line, point } of near_tracks) {
                    if (this.nearby_track_lines.indexOf(track_line) < 0) {
                        track_line.selected_point = point;
                        this.nearby_track_lines.push(track_line);
                    }
                }
            }
        }

        if (this.nearby_track_lines.length === 1) {
            this.select_track_line(this.nearby_track_lines[0].uuid);
            this.wait_track_line_selected = false;
        } else if (this.nearby_track_lines.length > 1) {
            g.track_edit.selected_track_line = null;
            this.show_track_line_select = true;

            const that = this;
            function wait_selection() {
                setTimeout(() => {
                    if (that.show_track_line_select) {
                        wait_selection();
                    } else {
                        that.wait_track_line_selected = false;
                    }
                }, 10);
            }
            wait_selection();
        } else {
            this.wait_track_line_selected = false;
        }
    }

    get_max_zoom() {
        let max_zoom = g.MAX_ZOOM;
        for (const map_source of g.map_source_list) {
            if (map_source.visible && map_source.zoom_max < max_zoom) {
                max_zoom = map_source.zoom_max;
            }
        }
        return max_zoom;
    }

    get_min_zoom() {
        let min_zoom = 1;
        for (const map_source of g.map_source_list) {
            if (map_source.visible && map_source.zoom_min > min_zoom) {
                min_zoom = map_source.zoom_min;
            }
        }
        return min_zoom;
    }

    get_spx_from_fpx(fpx: number) {
        return this.transformService.get_px_from_fpx(fpx, this.zoom) - this.corner1_px;
    }

    get_spy_from_fpy(fpy: number) {
        return this.transformService.get_py_from_fpy(fpy, this.zoom) - this.corner1_py;
    }

    get_spx_from_px(px: number) {
        return px - this.centre_px + this.half_width;
    }

    get_spy_from_px(py: number) {
        return py - this.centre_py + this.half_height;
    }

    get_fpx_from_spx(spx: number) {
        return this.transformService.get_fpx_from_px((spx - this.half_width) + this.centre_px, this.zoom)
    }

    get_fpy_from_spy(spy: number) {
        return this.transformService.get_fpy_from_py((spy - this.half_height) + this.centre_py, this.zoom)
    }

    get_lon_from_spx(spx: number) {
        return this.transformService.get_longitude_from_fpx(this.get_fpx_from_spx(spx));
    }

    get_lat_from_spy(spy: number) {
        return this.transformService.get_latitude_from_fpy(this.get_fpy_from_spy(spy));
    }

    pos_in_canvas(x: number, y: number) {
        return x >= 0 && x < this.width && y >= 0 && y < this.height;
    }

    track_line_not_in_canvas(track_line: TrackLine | EditTrackLine) {
        const px_min = this.transformService.get_px_from_fpx(track_line.fpx_min!, this.zoom);
        const px_max = this.transformService.get_px_from_fpx(track_line.fpx_max!, this.zoom);
        const py_min = this.transformService.get_py_from_fpy(track_line.fpy_min!, this.zoom);
        const py_max = this.transformService.get_py_from_fpy(track_line.fpy_max!, this.zoom);
        return px_min > this.corner2_px || px_max < this.corner1_px || py_min > this.corner2_py || py_max < this.corner1_py
    }


    pan(delta_px: number, delta_py: number) {
        const delta_fpx = this.transformService.get_fpx_from_px(delta_px, this.zoom);
        const delta_fpy = this.transformService.get_fpy_from_py(delta_py, this.zoom);
        this.zoom_to_fpx_fpy(this.zoom,
            this.centre_fpx + delta_fpx,
            this.centre_fpy + delta_fpy,
        );
    }

    zoom_in() {
        if (this.zoom < this.get_max_zoom()) {
            this.zoom_to_fpx_fpy(this.zoom + 1, this.centre_fpx, this.centre_fpy);
        }
    }

    zoom_out() {
        if (this.zoom > this.get_min_zoom()) {
            this.zoom_to_fpx_fpy(this.zoom - 1, this.centre_fpx, this.centre_fpy);
        }
    }

    zoom_to_fit_China() {
        const fpx_min = this.transformService.get_fpx_from_longitude(73.5);  // 中国陆地大致经度范围
        const fpx_max = this.transformService.get_fpx_from_longitude(135.05);  // 中国陆地大致经度范围
        const fpy_min = this.transformService.get_fpy_from_latitude(3.5);  // 中国陆地大致纬度范围
        const fpy_max = this.transformService.get_fpy_from_latitude(53.55);  // 中国陆地大致纬度范围
        this.zoom_to_range(fpx_min, fpx_max, fpy_min, fpy_max);
    }

    zoom_to_track(track_line: TrackLine) {
        this.zoom_to_range(track_line.fpx_min!, track_line.fpx_max!, track_line.fpy_min!, track_line.fpy_max!);
    }

    // 缩放地图使能够最大程度地显示相应范围所确定的轨迹
    zoom_to_range(min_fpx: number, max_fpx: number, min_fpy: number, max_fpy: number) {
        const diff_fpx = Math.abs(max_fpx - min_fpx);
        const diff_fpy = Math.abs(max_fpy - min_fpy);

        let zoom = g.MAX_ZOOM;
        while (zoom >= 1) {
            if (this.transformService.get_px_from_fpx(diff_fpx, zoom) < this.width && this.transformService.get_py_from_fpy(diff_fpy, zoom) < this.height) {
                break;
            }
            zoom -= 1;
        }

        if (zoom < 1) zoom = 1;

        this.zoom_to_fpx_fpy(zoom, Math.floor((min_fpx + max_fpx) / 2), Math.floor((min_fpy + max_fpy) / 2));
    }

    zoom_to_lon_lat(zoom: number, lon: number, lat: number) {
        this.zoom_to_fpx_fpy(zoom, this.transformService.get_fpx_from_longitude(lon), this.transformService.get_fpy_from_latitude(lat));
    }

    zoom_to_fpx_fpy(zoom: number, fpx: number, fpy: number) {
        // (this.zoom, this.centerFpx, this.centreFpy)：当前缩放级别，以及当前屏幕中心点在像素地图全图中的像素坐标
        // 当需要在屏幕窗口中重新刷新显示地图时，只需要确定当前的缩放级别和屏幕中心点的全图像素坐标，就可以唯一确定所有需要显示的地图瓦片
        this.zoom = zoom;
        this.centre_fpx = fpx;
        this.centre_fpy = fpy;
        this.centre_px = this.transformService.get_px_from_fpx(this.centre_fpx, this.zoom); // 屏幕中心点在当前缩放级别像素地图中的像素坐标
        this.centre_py = this.transformService.get_py_from_fpy(this.centre_fpy, this.zoom);  // 屏幕中心点在当前缩放级别像素地图中的像素坐标

        this.check_map_bound();

        const centre_lon_wgs84 = this.transformService.get_longitude_from_fpx(this.centre_fpx);
        const centre_lat_wgs84 = this.transformService.get_latitude_from_fpy(this.centre_fpy);
        const { gcj_lon, gcj_lat } = this.jiupianService.wgs84_to_gcj02(centre_lon_wgs84, centre_lat_wgs84);
        this.centre_fpx_gcj02 = this.transformService.get_fpx_from_longitude(gcj_lon);
        this.centre_fpy_gcj02 = this.transformService.get_fpy_from_latitude(gcj_lat);
        this.centre_px_gcj02 = this.transformService.get_px_from_fpx(this.centre_fpx_gcj02, this.zoom);
        this.centre_py_gcj02 = this.transformService.get_py_from_fpy(this.centre_fpy_gcj02, this.zoom);


        this.corner1_px = this.centre_px - this.half_width;
        this.corner1_py = this.centre_py - this.half_height;
        this.corner2_px = this.centre_px + this.half_width;
        this.corner2_py = this.centre_py + this.half_height;
        this.corner1_px_gcj02 = this.centre_px_gcj02 - this.half_width;
        this.corner1_py_gcj02 = this.centre_py_gcj02 - this.half_height;
        this.corner2_px_gcj02 = this.centre_px_gcj02 + this.half_width;
        this.corner2_py_gcj02 = this.centre_py_gcj02 + this.half_height;

        this.corner1_tile_x = Math.floor(this.corner1_px / g.MAP_TILE_SIZE);  // 由位置像素坐标获取tile坐标
        this.corner1_tile_y = Math.floor(this.corner1_py / g.MAP_TILE_SIZE);
        this.corner2_tile_x = Math.floor(this.corner2_px / g.MAP_TILE_SIZE);
        this.corner2_tile_y = Math.floor(this.corner2_py / g.MAP_TILE_SIZE);
        this.corner1_tile_x_gcj02 = Math.floor(this.corner1_px_gcj02 / g.MAP_TILE_SIZE);  // 由位置像素坐标获取tile坐标
        this.corner1_tile_y_gcj02 = Math.floor(this.corner1_py_gcj02 / g.MAP_TILE_SIZE);
        this.corner2_tile_x_gcj02 = Math.floor(this.corner2_px_gcj02 / g.MAP_TILE_SIZE);
        this.corner2_tile_y_gcj02 = Math.floor(this.corner2_py_gcj02 / g.MAP_TILE_SIZE);

        this.corner1_px_offset_in_tile = this.corner1_px % g.MAP_TILE_SIZE;  // 由位置像素坐标获取其在所处tile中与左上角像素的偏移值
        if (this.corner1_px_offset_in_tile < 0) {
            this.corner1_px_offset_in_tile += g.MAP_TILE_SIZE; // JavaScript中的%运算，当负数时结果与Python中不同，应纠正为与Python一致
        }
        this.corner1_py_offset_in_tile = this.corner1_py % g.MAP_TILE_SIZE;
        if (this.corner1_py_offset_in_tile < 0) {
            this.corner1_py_offset_in_tile += g.MAP_TILE_SIZE; // JavaScript中的%运算，当负数时结果与Python中不同，应纠正为与Python一致
        }
        this.corner1_px_gcj02_offset_in_tile = this.corner1_px_gcj02 % g.MAP_TILE_SIZE  // 由位置像素坐标获取其在所处tile中与左上角像素的偏移值
        if (this.corner1_px_gcj02_offset_in_tile < 0) {
            this.corner1_px_gcj02_offset_in_tile += g.MAP_TILE_SIZE; // JavaScript中的%运算，当负数时结果与Python中不同，应纠正为与Python一致
        }
        this.corner1_py_gcj02_offset_in_tile = this.corner1_py_gcj02 % g.MAP_TILE_SIZE
        if (this.corner1_py_gcj02_offset_in_tile < 0) {
            this.corner1_py_gcj02_offset_in_tile += g.MAP_TILE_SIZE; // JavaScript中的%运算，当负数时结果与Python中不同，应纠正为与Python一致
        }

        this.repaint();
    }

    async repaint() {
        if (!this.zoom) return;

        this.photo_drawing_count ++;

        this.ctx.clearRect(0, 0, this.width, this.height);
        await this.draw_maps();  // 需要await，保证画完地图后再画轨迹

        if (!g.editing) {
            this.draw_tracks(); // 轨迹绘制是同步的

            // 备份至离屏canvas
            this.backup_canvas.width = this.canvas.width;
            this.backup_canvas.height = this.canvas.height;
            this.backup_ctx.drawImage(this.canvas, 0, 0);
            this.draw_selected_track();
        } else {
            this.draw_edit_tracks();
        }
    }

    async repaint_over_backup() {
        if (!this.zoom) return;

        this.photo_drawing_count ++;

        this.ctx.clearRect(0, 0, this.width, this.height);
        this.ctx.drawImage(this.backup_canvas, 0, 0);
        this.draw_selected_track();
    }

    draw_selected_track() {
        if (!!g.user_data.selected) {
            if (g.user_data.selected.type === 'track_line') {
                this.draw_a_track_line(g.user_data.selected, true);  // 先画当前选中轨迹的背景图
                this.draw_a_track_line(g.user_data.selected);  // 再在背景之上正常画当前选中轨迹
                this.draw_current_track_point(g.user_data.selected, g.track_chart.selected_point);
            } else if (g.user_data.selected.type === 'way_point') {
                this.draw_selected_way_point(g.user_data.selected);
            }
        }

        this.draw_selected_photo();
    }

    gen_tile_key(map_source: MapSource, x: number, y: number, z: number) {
        return `${map_source.ename}_${x}_${y}_${z}`;
    }

    gen_tile_url(map_source: MapSource, x: number, y: number, z: number) {
        let url = map_source.url;

        if (!!map_source.server_part) {
            const server_list = map_source.server_part.split(',')
            const server_rand = server_list[Math.floor(Math.random() * server_list.length)];
            url = url.replace('{$serverpart}', server_rand);
        }

        // 腾讯地图
        if (map_source.url.includes('map.gtimg.com')) {
            y = (1 << z) - 1 - y;
            const dx = Math.floor(x / 16);
            const dy = Math.floor(y / 16);
            url = url.replace('{$dx}', dx + '').replace('{$dy}', dy + '')
        }

        url = url.replace('{$x}', x + '').replace('{$y}', y + '').replace('{$z}', z + '');
        return url;;
    }

    async draw_maps() {
        for (let i = g.map_source_list.length - 1; i >= 0; i--) {
            await this.draw_a_map(g.map_source_list[i]);   // 需要await，维持各地图层绘制的先后顺序
        }
    }

    async draw_a_map(map_source: MapSource) {
        if (!map_source.visible) {
            return;
        }

        const MAX_TILE = 1 << this.zoom;

        let corner1_tile_x: number;
        let corner1_tile_y: number;
        let corner2_tile_x: number;
        let corner2_tile_y: number;
        let corner1_px_offset_in_tile: number;
        let corner1_py_offset_in_tile: number;
        if (this.need_jiupian() && map_source.coordinates === 'GCJ02') {
            corner1_tile_x = this.corner1_tile_x_gcj02;
            corner1_tile_y = this.corner1_tile_y_gcj02;
            corner2_tile_x = this.corner2_tile_x_gcj02;
            corner2_tile_y = this.corner2_tile_y_gcj02;
            corner1_px_offset_in_tile = this.corner1_px_gcj02_offset_in_tile;
            corner1_py_offset_in_tile = this.corner1_py_gcj02_offset_in_tile;
        } else {
            corner1_tile_x = this.corner1_tile_x;
            corner1_tile_y = this.corner1_tile_y;
            corner2_tile_x = this.corner2_tile_x;
            corner2_tile_y = this.corner2_tile_y;
            corner1_px_offset_in_tile = this.corner1_px_offset_in_tile;
            corner1_py_offset_in_tile = this.corner1_py_offset_in_tile;
        }

        const promises = [];

        for (let tile_y = corner1_tile_y; tile_y <= corner2_tile_y; tile_y++) {
            const spy = (tile_y - corner1_tile_y) * g.MAP_TILE_SIZE - corner1_py_offset_in_tile;
            for (let tile_x = corner1_tile_x; tile_x <= corner2_tile_x; tile_x++) {
                const spx = (tile_x - corner1_tile_x) * g.MAP_TILE_SIZE - corner1_px_offset_in_tile;

                // 当zoom_out至整个世界地图小于屏幕窗口时，地图之外的位置显示空白图片，不下载直接绘制
                // 在中国地图右侧重复显示一半世界地图（美洲），以使中国地图可始终居中
                if (tile_y < 0 || tile_y >= MAX_TILE || tile_x < 0 || tile_x >= Math.floor(MAX_TILE * 3 / 2)) {
                    // 不画
                    continue;
                }

                const new_tile_x = tile_x < MAX_TILE ? tile_x : tile_x % MAX_TILE;  // 针对右侧重复显示的一半地图

                const tile_key = this.gen_tile_key(map_source, new_tile_x, tile_y, this.zoom);
                const tile_url = this.gen_tile_url(map_source, new_tile_x, tile_y, this.zoom);

                promises.push(this.render_tile(tile_key, tile_url, spx, spy));
            }
        }

        await Promise.all(promises);
    }

    // 渲染单个瓦片
    async render_tile(tile_key: string, tile_url: string, spx: number, spy: number) {
        try {
            const img = await this.get_tile_image(tile_key, tile_url);
            if (!img) return;

            this.ctx.drawImage(
                img,
                spx,
                spy,
                g.MAP_TILE_SIZE,
                g.MAP_TILE_SIZE
            );
        } catch (error) {
            // console.warn('Failed to render tile:', error);
        }
    }

    // 获取瓦片图片
    async get_tile_image(tile_key: string, tile_url: string) {
        // 检查缓存
        if (this.tile_cache.has(tile_key)) {
            // 因为是 async 函数，返回时会自动包装成一个Promise对象
            // 如果本函数前面不写 “async”，在通过 await 调用时，返回时也会被自动包装成一个Promise对象
            // 因此，本函数前面写不写 “async” 都是可以的
            return this.tile_cache.get(tile_key);
        }

        // 防止重复加载
        if (this.loading_tiles.has(tile_key)) {
            return null;
        }

        this.loading_tiles.add(tile_key);

        try {
            const img = new Image();

            return new Promise((resolve, reject) => {
                img.onload = () => {
                    this.tile_cache.set(tile_key, img);
                    this.loading_tiles.delete(tile_key);
                    resolve(img);
                };
                img.onerror = () => {
                    this.loading_tiles.delete(tile_key);
                    reject(new Error('Failed to load tile: ' + tile_url));
                };
                img.crossOrigin = 'anonymous';
                img.src = tile_url;
            });
        } catch (error) {
            this.loading_tiles.delete(tile_key);
            // console.error('Error loading tile:', error);
            return null;
        }
    }

    check_map_bound(): boolean {
        let centre_changed = false;

        // 在中国地图右侧重复显示一半世界地图（美洲），以使中国地图可始终居中
        const map_display_width = Math.floor((g.MAP_TILE_SIZE << this.zoom) * 3 / 2);
        if (map_display_width <= this.width) {
            // 当地图尺寸缩小至小于屏幕窗口尺寸时，将地图中心点作为屏幕中心点，固定显示于窗口正中央
            this.centre_px = Math.floor(map_display_width / 2);
            centre_changed = true;
        } else if (this.centre_px < this.half_width) {
            // 地图宽度大于屏幕窗口宽度，当向右拖拽地图使地图左边界离开屏幕左边界时，使两个边界重叠锚定，不再向右偏离
            // 即：this._centre_px最小值应为this._half_width
            this.centre_px = this.half_width;
            centre_changed = true;
        } else if ((this.centre_px + this.half_width) > map_display_width) {
            // 地图宽度大于屏幕窗口宽度，当向左拖拽地图使地图右边界离开屏幕右边界时，使两个边界重叠锚定，不再向左偏离
            // 即：this._centre_px最大值应为map_display_width - this._half_width
            this.centre_px = map_display_width - this.half_width;
            centre_changed = true;
        }

        const map_display_height = g.MAP_TILE_SIZE << this.zoom;
        if (map_display_height <= this.height) {
            this.centre_py = Math.floor(map_display_height / 2);
            centre_changed = true;
        } else if (this.centre_py < this.half_height) {
            this.centre_py = this.half_height;
            centre_changed = true;
        } else if ((this.centre_py + this.half_height) > map_display_height) {
            this.centre_py = map_display_height - this.half_height;
            centre_changed = true
        }

        if (centre_changed) {
            this.centre_fpx = this.transformService.get_fpx_from_px(this.centre_px, this.zoom)
            this.centre_fpy = this.transformService.get_fpy_from_py(this.centre_py, this.zoom)
        }

        return centre_changed
    }

    need_jiupian() {
        return this.zoom > 4 // 只有显示级别大于4时，才进行地图纠偏
    }

    draw_tracks() {
        this.pos_to_waypoint_or_photo.clear();

        const begin = (new Date()).getTime();
        let timeout = false;

        for (const node of g.visible_tracks.values()) {
            // 拖动过程中如果超时则不画后面的轨迹
            if (this.dragging_map && ((new Date()).getTime() - begin > 8)) {
                timeout = true;
                break;
            }

            if (!node) continue;  // 刚打开应用初始化轨迹树时，visible_tracks中的至可能为null

            if (node.type === 'track_line') {
                this.draw_a_track_line(node);
            } else if (node.type === 'way_point') {
                this.draw_way_point(node);
            }
        }

        if (timeout) return;

        this.draw_photos();
    }

    draw_a_track_line(track_line: TrackLine, background = false) {
        if (!track_line || !track_line.visible || this.track_line_not_in_canvas(track_line)) {
            return;
        }

        // this.ctx.save();

        if (!background) {
            // 正常轨迹
            this.ctx.lineWidth = track_line.width!;
            this.ctx.strokeStyle = `rgba(${track_line.red},${track_line.green},${track_line.blue},${track_line.alpha})`;
        } else {
            // 当前选中轨迹的背景图
            this.ctx.lineWidth = track_line.width! + 4;
            this.ctx.strokeStyle = `rgba(${255 - track_line.red!},${255 - track_line.green!},${255 - track_line.blue!},155)`;
        }
        this.ctx.lineJoin = 'round';
        this.ctx.lineCap = 'round';
        this.ctx.setLineDash([]);

        this.ctx.beginPath();

        const track_line_points = g.track_line_points_cache.get(track_line.uuid);

        let firstPoint = true;
        let start_spx, start_spy, end_spx, end_spy;
        for (const point of track_line_points) {
            const [fpx, fpy] = [point[4], point[5]];
            const spx = this.get_spx_from_fpx(fpx);
            const spy = this.get_spy_from_fpy(fpy);

            if (firstPoint) {
                [start_spx, start_spy] = [spx, spy];
                [end_spx, end_spy] = [spx, spy];
                this.ctx.moveTo(spx, spy);
                firstPoint = false;
            } else {
                if (spx === end_spx && spy === end_spy) {
                    continue;
                }

                if (this.pos_in_canvas(end_spx!, end_spy!)) {
                    // 上一点在画布内，无论当前点在不在画布内，都需要画至当前点
                    this.ctx.lineTo(spx, spy);
                } else if (this.pos_in_canvas(spx, spy)) {
                    // 上一点不在画布内，当前点在画布内，需要先移动到上一点并画入至当前点
                    this.ctx.moveTo(end_spx!, end_spy!);
                    this.ctx.lineTo(spx, spy);
                } // else {} // 上一点不在画布内，当前点也不在画布内，不画
                
                [end_spx, end_spy] = [spx, spy];
            }
        }

        this.ctx.stroke();

        // 绘制起点和终点标记
        if (track_line.draw_endpoints && track_line_points.length > 0) {
            // 绘制起点
            if (this.pos_in_canvas(start_spx!, start_spy!)) {
                this.ctx.fillStyle = '#00ff00';
                this.ctx.beginPath();
                this.ctx.arc(start_spx!, start_spy!, Math.ceil(track_line.width!) / 2 + 2, 0, 2 * Math.PI);
                this.ctx.fill();
                this.ctx.strokeStyle = '#ffff00';
                this.ctx.lineWidth = 1;
                this.ctx.lineJoin = 'round';
                this.ctx.lineCap = 'round';
                this.ctx.setLineDash([]);
                this.ctx.stroke();                
            }

            // 绘制终点
            if (this.pos_in_canvas(end_spx!, end_spy!)) {
                this.ctx.fillStyle = '#ff0000';
                this.ctx.beginPath();
                this.ctx.arc(end_spx!, end_spy!, Math.ceil(track_line.width!) / 2 + 2, 0, 2 * Math.PI);
                this.ctx.fill();
                this.ctx.strokeStyle = '#ffff00';
                this.ctx.lineWidth = 1;
                this.ctx.lineJoin = 'round';
                this.ctx.lineCap = 'round';
                this.ctx.setLineDash([]);
                this.ctx.stroke();                
            }
        }

        // this.ctx.restore();
    }

    draw_current_track_point(track_line: TrackLine, point_index: number) {
        if (point_index < 0) return;

        if (!track_line.visible || this.track_line_not_in_canvas(track_line)) return;

        // this.ctx.save();

        const track_line_points = g.track_line_points_cache.get(track_line.uuid);
        const point = track_line_points[point_index]

        const fpx = point[4];
        const fpy = point[5];
        const spx = this.get_spx_from_fpx(fpx);
        const spy = this.get_spy_from_fpy(fpy);

        this.ctx.lineWidth = 1;
        this.ctx.strokeStyle = '#ff0000';
        this.ctx.lineJoin = 'round';
        this.ctx.lineCap = 'round';
        this.ctx.setLineDash([]);

        this.ctx.beginPath();
        this.ctx.moveTo(0, spy);
        this.ctx.lineTo(this.width, spy);
        this.ctx.stroke();

        this.ctx.moveTo(spx, 0);
        this.ctx.lineTo(spx, this.height);
        this.ctx.stroke();

        // const label = `东经：${point[0].toFixed(8)}，北纬：${point[1].toFixed(8)}，海拔：${point[2].toFixed(0)}，时间：${this.transformService.utc2local(point[3])}`
        // this.ctx.font = '12px serif';
        // this.ctx.fillStyle = '#ff0000';
        // this.ctx.fillText(label, spx + 10, spy - 10);

        // this.ctx.restore();
    }

    change_track_line_color() {
        g.user_data.selected.red = parseInt(this.track_line_color.substring(1, 3), 16);
        g.user_data.selected.green = parseInt(this.track_line_color.substring(3, 5), 16);
        g.user_data.selected.blue = parseInt(this.track_line_color.substring(5, 7), 16);
        g.user_data.trackLineService.compute_track_line_thumbnail(g.user_data.selected);
        this.repaint();
        g.track_chart.refresh();
        g.track_info.refresh();
    }

    draw_way_point(way_point: WayPoint) {
        if (!way_point.visible) return;

        const spx = this.get_spx_from_fpx(way_point.fpx!) - 16;
        const spy = this.get_spy_from_fpy(way_point.fpy!) - 32;

        if (!this.pos_in_canvas(spx, spy)) return;

        this.ctx.drawImage(wpt_images[way_point.pin_index!], spx, spy);

        for (let y = spy; y < spy + 32; y++) {
            for (let x = spx; x < spx + 32; x++) {
                const pos_key = `pos-${x}-${y}`;
                this.pos_to_waypoint_or_photo.set(pos_key, way_point) // 添加坐标与wpt对应关系，以便鼠标选择；后画的覆盖前面画的，只对应一个
            }
        }
    }

    draw_selected_way_point(way_point: WayPoint) {
        if (!way_point.visible) return;

        const spx = this.get_spx_from_fpx(way_point.fpx!);
        const spy = this.get_spy_from_fpy(way_point.fpy!);

        if (!this.pos_in_canvas(spx, spy)) return;

        this.ctx.drawImage(wpt_images[way_point.pin_index!], spx - 16, spy - 32);

        // 十字交叉线
        // this.ctx.save();
        this.ctx.lineWidth = 1;
        this.ctx.strokeStyle = '#ff0000';
        this.ctx.lineJoin = 'round';
        this.ctx.lineCap = 'round';
        this.ctx.setLineDash([]);

        this.ctx.beginPath();
        this.ctx.moveTo(0, spy);
        this.ctx.lineTo(this.width, spy);
        this.ctx.stroke();
        this.ctx.moveTo(spx, 0);
        this.ctx.lineTo(spx, this.height);
        this.ctx.stroke();
        // this.ctx.restore();

        for (let y = spy - 32; y < spy; y++) {
            for (let x = spx - 16; x < spx + 16; x++) {
                const pos_key = `pos-${x}-${y}`;
                this.pos_to_waypoint_or_photo.set(pos_key, way_point) // 添加坐标与wpt对应关系，以便鼠标选择；后画的覆盖前面画的，只对应一个
            }
        }
    }

    draw_photos() {
        for (const photo of g.photos.values()) {
            const spx = this.get_spx_from_fpx(photo.fpx!) - 16;
            const spy = this.get_spy_from_fpy(photo.fpy!) - 16;

            if (!this.pos_in_canvas(spx, spy)) return;

            this.ctx.drawImage(photo.image, spx, spy, 32, 32);

            for (let y = spy; y < spy + 32; y++) {
                for (let x = spx; x < spx + 32; x++) {
                    const pos_key = `pos-${x}-${y}`;
                    this.pos_to_waypoint_or_photo.set(pos_key, photo) // 添加坐标与photo对应关系，以便鼠标选择；后画的覆盖前面画的，只对应一个
                }
            }
        }
    }

    draw_selected_photo() {
        const photo: Photo = g.selected_photo;
        if (!photo) return;

        const spx = this.get_spx_from_fpx(photo.fpx!);
        const spy = this.get_spy_from_fpy(photo.fpy!);

        if (!this.pos_in_canvas(spx, spy)) return;

        this.ctx.drawImage(photo.image, spx - 16, spy - 16, 32, 32);

        for (let y = spy - 16; y < spy + 16; y++) {
            for (let x = spx - 1; x < spx + 16; x++) {
                const pos_key = `pos-${x}-${y}`;
                this.pos_to_waypoint_or_photo.set(pos_key, photo) // 添加坐标与photo对应关系，以便鼠标选择；后画的覆盖前面画的，只对应一个
            }
        }

        const that = this;
        const photo_drawing_count = this.photo_drawing_count;
        let spx2: number, spy2: number;
        let offset = 10;
        function draw_photo_direction() {
            if (photo_drawing_count !== that.photo_drawing_count) return;

            that.ctx.beginPath();
            that.ctx.lineWidth = 1;
            that.ctx.lineJoin = 'round';
            that.ctx.lineCap = 'round';
            that.ctx.setLineDash([]);
            that.ctx.strokeStyle = '#ff0000';
            that.ctx.moveTo(spx, spy);
            that.ctx.lineTo(spx2, spy2);
            that.ctx.stroke();

            that.ctx.setLineDash([10, 5]);
            that.ctx.lineDashOffset = offset;
            that.ctx.strokeStyle = '#ffff00';
            that.ctx.moveTo(spx, spy);
            that.ctx.lineTo(spx2, spy2);
            that.ctx.stroke();

            offset --;
            if (offset < 0) offset = 10;

            setTimeout(draw_photo_direction, 20);

        }

         // 方向线
        if (!!photo.direction) {
            const angle = Math.PI * photo.direction! / 180;
            const line2 = 2 * this.width;
            const x_delta2 = line2 * Math.sin(angle);
            const y_delta2 = line2 * Math.cos(angle);
            spx2 = spx + Math.floor(x_delta2);
            spy2 = spy - Math.floor(y_delta2);

            draw_photo_direction();
        }
    }

    loadImage(src: string) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => resolve(img);
            img.onerror = (err) => reject(err);
            img.src = src;
        });
    }

    draw_edit_tracks() {
        this.edit_pos_to_track_lines.clear();

        // 先画未被check的轨迹
        for (const track_line of g.track_edit.track_line_list) {
            if (!track_line.checked) {
                this.draw_edit_track_line(track_line);
            }
        }

        // 再画被check的轨迹
        for (const track_line of g.track_edit.track_line_list) {
            if (track_line.checked) {
                this.draw_edit_track_line(track_line);
            }
        }

        // 再画当前选中轨迹
        this.draw_edit_selected_track_line();

        // 画自绘轨迹橡皮筋
        this.draw_edit_self_drawing_rubber_band();
        // 画拖拽点编辑橡皮筋
        this.draw_edit_dragging_point_rubber_band();
    }

    draw_edit_track_line(track_line: EditTrackLine) {
        // selected轨迹之后还要画，这里先不画
        if (track_line.selected || this.track_line_not_in_canvas(track_line)) {
            return;
        }

        if (track_line.points!.length < 1) return;

        // 只有一个点时，画作起点
        if (track_line.points!.length === 1) {
            const point = track_line.points![0];
            const [fpx, fpy] = [point[4], point[5]];
            const spx = this.get_spx_from_fpx(fpx);
            const spy = this.get_spy_from_fpy(fpy);

            // this.ctx.save();
            this.ctx.fillStyle = '#00ff00';
            this.ctx.beginPath();
            this.ctx.arc(spx!, spy!, 3, 0, 2 * Math.PI);
            this.ctx.fill();

            this.ctx.strokeStyle = '#ffff00';
            this.ctx.lineWidth = 1;
            this.ctx.lineJoin = 'round';
            this.ctx.lineCap = 'round';
            this.ctx.setLineDash([]);

            this.ctx.stroke();
            // this.ctx.restore();
            return;
        }

        // this.ctx.save();

        this.ctx.lineWidth = 2;
        this.ctx.strokeStyle = track_line.checked ? '#ffff00ff' : '#0000ffff';
        this.ctx.lineJoin = 'round';
        this.ctx.lineCap = 'round';
        this.ctx.setLineDash([]);

        this.ctx.beginPath();
        let start_spx, start_spy, end_spx, end_spy;
        const circle_points = [];
        for (let i = 0; i < track_line.points!.length; i++) {
            const point = track_line.points![i];
            const [fpx, fpy] = [point[4], point[5]];
            const spx = this.get_spx_from_fpx(fpx);
            const spy = this.get_spy_from_fpy(fpy);

            if (i === 0) {
                [start_spx, start_spy] = [spx, spy];
                [end_spx, end_spy] = [spx, spy];
                this.ctx.moveTo(spx, spy);
            } else {
                if (spx === end_spx && spy === end_spy) {
                    continue;
                }

                // 编辑区轨迹较少，画布外的点也画，不需优化
                this.ctx.lineTo(spx, spy);

                [end_spx, end_spy] = [spx, spy];
                circle_points.push([spx, spy]);
            }

            if (this.pos_in_canvas(spx, spy)) {
                const pos_key = 'pos-' + spx + '-' + spy;
                const pos_value = this.edit_pos_to_track_lines.get(pos_key);
                if (!pos_value) {
                    this.edit_pos_to_track_lines.set(pos_key, [{ track_line, point: i }]);
                } else {
                    pos_value.push({ track_line, point: i });
                }
            }

        }
        this.ctx.stroke();

        // 画除起终点之外的点
        circle_points.pop();
        this.ctx.fillStyle = track_line.checked ? '#ffff00ff' : '#0000ffff';
        for (const point of circle_points) {
            this.ctx.beginPath();
            this.ctx.arc(point[0], point[1], 2, 0, 2 * Math.PI);
            this.ctx.fill();
        }

        // 绘制起点和终点标记
        if (track_line.points!.length > 0) {
            // 绘制起点
            this.ctx.fillStyle = '#00ff00';
            this.ctx.beginPath();
            this.ctx.arc(start_spx!, start_spy!, 3, 0, 2 * Math.PI);
            this.ctx.fill();
            this.ctx.strokeStyle = '#ffff00';
            this.ctx.lineWidth = 1;
            this.ctx.lineJoin = 'round';
            this.ctx.lineCap = 'round';
            this.ctx.setLineDash([]);
            this.ctx.stroke();

            // 绘制终点
            this.ctx.fillStyle = '#ff0000';
            this.ctx.beginPath();
            this.ctx.arc(end_spx!, end_spy!, 3, 0, 2 * Math.PI);
            this.ctx.fill();
            this.ctx.strokeStyle = '#ffff00';
            this.ctx.lineWidth = 1;
            this.ctx.lineJoin = 'round';
            this.ctx.lineCap = 'round';
            this.ctx.setLineDash([]);
            this.ctx.stroke();
        }

        // this.ctx.restore();
    }

    draw_edit_selected_track_line() {
        const track_line: EditTrackLine = g.track_edit.selected_track_line;

        if (!track_line || this.track_line_not_in_canvas(track_line)) {
            return;
        }

        // this.ctx.save();
        this.ctx.lineWidth = 2;
        this.ctx.lineJoin = 'round';
        this.ctx.lineCap = 'round';
        this.ctx.setLineDash([]);

        // 选中轨迹段之前
        this.ctx.strokeStyle = track_line.checked ? '#ffff00ff' : '#0000ffff';
        this.ctx.beginPath();
        let last_spx, last_spy;
        let circle_points = [];
        for (let i = 0; i <= track_line.sel_start_idx!; i++) {
            const point = track_line.points![i];
            const [fpx, fpy] = [point[4], point[5]];
            const spx = this.get_spx_from_fpx(fpx);
            const spy = this.get_spy_from_fpy(fpy);

            if (i === 0) {
                [last_spx, last_spy] = [spx, spy];
                this.ctx.moveTo(spx, spy);
            } else {
                if (spx === last_spx && spy === last_spy) {
                    continue;
                }
                this.ctx.lineTo(spx, spy);

                [last_spx, last_spy] = [spx, spy];

                // 线段要画到选中段起始点，但暂不画该点
                if (i !== track_line.sel_start_idx) {
                    circle_points.push([spx, spy]);
                }
            }

            if (this.pos_in_canvas(spx, spy)) {
                const pos_key = 'pos-' + spx + '-' + spy;
                const pos_value = this.edit_pos_to_track_lines.get(pos_key);
                if (!pos_value) {
                    this.edit_pos_to_track_lines.set(pos_key, [{ track_line, point: i }]);
                } else {
                    pos_value.push({ track_line, point: i });
                }
            }
        }
        this.ctx.stroke();

        this.ctx.fillStyle = track_line.checked ? '#ffff00ff' : '#0000ffff';
        for (const point of circle_points) {
            this.ctx.beginPath();
            this.ctx.arc(point[0], point[1], 2, 0, 2 * Math.PI);
            this.ctx.fill();
        }

        // 选中轨迹段
        this.ctx.strokeStyle = '#00ff00ff';
        this.ctx.beginPath();
        circle_points = [];
        for (let i = track_line.sel_start_idx!; i <= track_line.sel_end_idx!; i++) {
            const point = track_line.points![i];
            const [fpx, fpy] = [point[4], point[5]];
            const spx = this.get_spx_from_fpx(fpx);
            const spy = this.get_spy_from_fpy(fpy);

            if (i === 0) {
                [last_spx, last_spy] = [spx, spy];
                this.ctx.moveTo(spx, spy);
            } else if (i === track_line.sel_start_idx) {
                [last_spx, last_spy] = [spx, spy];
                this.ctx.moveTo(spx, spy);

                // 如果结束点小于起始点，则不存在选中段，此处不画点
                if (track_line.sel_end_idx! >= track_line.sel_start_idx!)
                    circle_points.push([spx, spy]);
            } else {
                if (spx === last_spx && spy === last_spy) {
                    continue;
                }
                this.ctx.lineTo(spx, spy);

                [last_spx, last_spy] = [spx, spy];
                circle_points.push([spx, spy]);
            }

            if (this.pos_in_canvas(spx, spy)) {
                const pos_key = 'pos-' + spx + '-' + spy;
                const pos_value = this.edit_pos_to_track_lines.get(pos_key);
                if (!pos_value) {
                    this.edit_pos_to_track_lines.set(pos_key, [{ track_line, point: i }]);
                } else {
                    pos_value.push({ track_line, point: i });
                }
            }
        }
        this.ctx.stroke();

        this.ctx.fillStyle = '#00ff00ff';
        for (const point of circle_points) {
            this.ctx.beginPath();
            this.ctx.arc(point[0], point[1], 2, 0, 2 * Math.PI);
            this.ctx.fill();
        }

        // 选中轨迹段之后
        this.ctx.strokeStyle = track_line.checked ? '#ffff00ff' : '#0000ffff';
        this.ctx.beginPath();
        this.ctx.moveTo(last_spx!, last_spy!);
        circle_points = [];

        // 如果结束点小于起始点，从起始点的下一点开始画
        for (let i = Math.max(track_line.sel_end_idx!, track_line.sel_start_idx!) + 1; i < track_line.points!.length; i++) {
            const point = track_line.points![i];
            const [fpx, fpy] = [point[4], point[5]];
            const spx = this.get_spx_from_fpx(fpx);
            const spy = this.get_spy_from_fpy(fpy);

            if (spx === last_spx && spy === last_spy) {
                continue;
            }
            this.ctx.lineTo(spx, spy);

            [last_spx, last_spy] = [spx, spy];
            circle_points.push([spx, spy]);

            if (this.pos_in_canvas(spx, spy)) {
                const pos_key = 'pos-' + spx + '-' + spy;
                const pos_value = this.edit_pos_to_track_lines.get(pos_key);
                if (!pos_value) {
                    this.edit_pos_to_track_lines.set(pos_key, [{ track_line, point: i }]);
                } else {
                    pos_value.push({ track_line, point: i });
                }
            }
        }
        this.ctx.stroke();

        this.ctx.fillStyle = track_line.checked ? '#ffff00ff' : '#0000ffff';
        for (const point of circle_points) {
            this.ctx.beginPath();
            this.ctx.arc(point[0], point[1], 2, 0, 2 * Math.PI);
            this.ctx.fill();
        }

        // 绘制起点和终点标记
        if (track_line.points!.length > 0) {
            // 绘制起点
            let point = track_line.points![0];
            let spx = this.get_spx_from_fpx(point[4]);
            let spy = this.get_spy_from_fpy(point[5]);
            this.ctx.fillStyle = '#00ff00';
            this.ctx.beginPath();
            this.ctx.arc(spx, spy, 3, 0, 2 * Math.PI);
            this.ctx.fill();
            this.ctx.strokeStyle = '#ffff00';
            this.ctx.lineWidth = 1;
            this.ctx.lineJoin = 'round';
            this.ctx.lineCap = 'round';
            this.ctx.setLineDash([]);
            this.ctx.stroke();

            // 绘制终点
            point = track_line.points![track_line.points!.length! - 1]
            spx = this.get_spx_from_fpx(point[4]);
            spy = this.get_spy_from_fpy(point[5]);
            this.ctx.fillStyle = '#ff0000';
            this.ctx.beginPath();
            this.ctx.arc(spx, spy, 3, 0, 2 * Math.PI);
            this.ctx.fill();
            this.ctx.strokeStyle = '#ffff00';
            this.ctx.lineWidth = 1;
            this.ctx.lineJoin = 'round';
            this.ctx.lineCap = 'round';
            this.ctx.setLineDash([]);
            this.ctx.stroke();
        }

        // 十字交叉线
        if (track_line.selected_point! >= 0) {
            const point = track_line.points![track_line.selected_point!];
            const fpx = point[4];
            const fpy = point[5];
            const spx = this.get_spx_from_fpx(fpx);
            const spy = this.get_spy_from_fpy(fpy);

            this.ctx.lineWidth = 1;
            this.ctx.strokeStyle = '#ff0000';
            this.ctx.lineJoin = 'round';
            this.ctx.lineCap = 'round';
            this.ctx.setLineDash([]);

            this.ctx.beginPath();
            this.ctx.moveTo(0, spy);
            this.ctx.lineTo(this.width, spy);
            this.ctx.stroke();

            this.ctx.moveTo(spx, 0);
            this.ctx.lineTo(spx, this.height);
            this.ctx.stroke();

            // const label = `东经：${point[0].toFixed(8)}，北纬：${point[1].toFixed(8)}，海拔：${point[2].toFixed(0)}，时间：${this.transformService.utc2local(point[3])}`
            // this.ctx.font = '12px serif';
            // this.ctx.fillStyle = '#ff0000';
            // this.ctx.fillText(label, spx + 10, spy - 10);
        }

        // this.ctx.restore();
    }

    enter_self_drawing() {
        if (this.self_drawing) return;

        this.self_drawing_end_pos = null;
        g.track_edit.select_track_line('');

        this.self_drawing_track_line = {
            type: 'track_line',
            label: '自绘轨迹' + (g.new_name_suffix++).toString().padStart(3, '0'),
            uuid: uuid().replace(/-/g, '').toLowerCase(),
            width: 2,
            red: 0,
            green: 0,
            blue: 255,
            alpha: 255,
            draw_endpoints: true,
            visible: true,
            has_timestamp: true,
            selected: false,
            checked: false,
            points: [],
        };
        g.track_edit.track_line_list.push(this.self_drawing_track_line);
        this.canvas.style.cursor = 'crosshair';
        this.self_drawing = true;
    }

    draw_edit_self_drawing_rubber_band() {
        if (this.self_drawing && !!this.self_drawing_track_line && !!this.self_drawing_end_pos) {
            this.draw_edit_track_line(this.self_drawing_track_line!);
            const fpx_pre = this.self_drawing_track_line!.points![this.self_drawing_track_line!.points!.length - 1][4]
            const fpy_pre = this.self_drawing_track_line!.points![this.self_drawing_track_line!.points!.length - 1][5]
            const spx_pre = this.get_spx_from_fpx(fpx_pre);
            const spy_pre = this.get_spy_from_fpy(fpy_pre);

            // this.ctx.save();
            this.ctx.lineWidth = 1;
            this.ctx.strokeStyle = '#ff0000';
            this.ctx.lineJoin = 'round';
            this.ctx.lineCap = 'round';
            this.ctx.setLineDash([]);

            this.ctx.beginPath();
            this.ctx.moveTo(spx_pre, spy_pre);
            this.ctx.lineTo(this.self_drawing_end_pos.spx, this.self_drawing_end_pos.spy);
            this.ctx.stroke();
            // this.ctx.restore();
        }
    }

    draw_edit_dragging_point_rubber_band() {
        if (this.dragging_point && !!this.self_drawing_end_pos) {
            const track_line = g.track_edit.selected_track_line;
            let prev_point = track_line.selected_point - 1;
            let next_point = track_line.selected_point + 1;

            if (prev_point < 0) {
                prev_point = next_point;
            }
            if (next_point > track_line.point_num - 1) {
                next_point = prev_point;
            }

            const prev_fpx = track_line.points[prev_point][4]
            const prev_fpy = track_line.points[prev_point][5]
            const prev_spx = this.get_spx_from_fpx(prev_fpx);
            const prev_spy = this.get_spy_from_fpy(prev_fpy);

            const next_fpx = track_line.points[next_point][4]
            const next_fpy = track_line.points[next_point][5]
            const next_spx = this.get_spx_from_fpx(next_fpx);
            const next_spy = this.get_spy_from_fpy(next_fpy);

            // this.ctx.save();
            this.ctx.lineWidth = 1;
            this.ctx.strokeStyle = '#ff0000';
            this.ctx.lineJoin = 'round';
            this.ctx.lineCap = 'round';
            this.ctx.setLineDash([]);

            this.ctx.beginPath();
            this.ctx.moveTo(prev_spx, prev_spy);
            this.ctx.lineTo(this.self_drawing_end_pos.spx, this.self_drawing_end_pos.spy);
            this.ctx.lineTo(next_spx, next_spy);
            this.ctx.stroke();
            // this.ctx.restore();
        }
    }

    async place_way_point() {
        const spx = this.context_menu_event.offsetX;
        const spy = this.context_menu_event.offsetY;
        const lon = this.get_lon_from_spx(spx);
        const lat = this.get_lat_from_spy(spy);

        const way_point: any = {
            type: 'way_point',
            uuid: uuid().replace(/-/g, '').toLowerCase(),
            pin_index: 0,
            visible: true,
            lon,
            lat,
            alt: 0,
            timestamp: this.transformService.utc2local((new Date()).toISOString()),
            fpx: this.get_fpx_from_spx(spx),
            fpy: this.get_fpy_from_spy(spy),
        }

        way_point.label = await this.mapApiService.get_address_from_location(lon, lat);
        way_point.alt = await this.srtmService.get_alt(lon, lat);

        g.root_node_list.push(way_point);
        g.visible_tracks.set(way_point.uuid, way_point);

        // 这里不能直接调用g.user_data.select_way_point，因为其中调用的是repaint_over_backup，没有把当前路点画到底图中；如果这时选择其他节点，底图中没有当前路点
        // g.user_data.select_way_point(way_point);
        g.user_data.selected = way_point;
        this.repaint();
        g.track_chart.refresh();
        g.track_info.refresh();
    }

    clip_map_canvas() {
        const img_url = this.canvas.toDataURL('image/png', 1); // format可以是"png", "jpeg"等

        this.electronService.invoke('save-map-screenshot', {
            filename: `spider-map-${(g.new_name_suffix ++).toString().padStart(3, '0')}.png`,
            img_url: img_url,
        })

        // const link = document.createElement('a');
        // link.href = image;
        // link.download = `spider-map-${g.new_name_suffix++}.png`;
        // link.click();
    }

}

